using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;

namespace MediaBackup
{
    public static class Process
    {
        #region Inners
        private class Output
        {
            System.Diagnostics.Process m_Process;
            StringBuilder m_Output = new StringBuilder(0x1000);

            public int Length
            {
                get
                {
                    return m_Output.Length;
                }
            }

            public Output(System.Diagnostics.Process process)
            {
                m_Process = process;
                if (!process.StartInfo.RedirectStandardOutput)
                    throw new InvalidOperationException("System.Diagnostics.Process needs to have been started with 'RedirectStandardOutput' enabled.");

                // BrianSp: It would be nice to be able to log more frequently than once per line, particularly due to the way the encoder outputs
                // periods to indicate progress.  Oh well.
                process.OutputDataReceived += DataReceivedEventHandler;
                process.ErrorDataReceived += DataReceivedEventHandler;
                process.BeginOutputReadLine();
            }

            public override string ToString()
            {
                return m_Output.ToString();
            }

            public void Cancel()
            {
                m_Process.CancelOutputRead();
                m_Process.OutputDataReceived -= DataReceivedEventHandler;
                m_Process.ErrorDataReceived -= DataReceivedEventHandler;
            }

            private void DataReceivedEventHandler(object sender, DataReceivedEventArgs args)
            {
                if (args.Data != null)
                {
                    m_Output.Append(args.Data);
                    m_Output.Append("\r\n");

                    // BrianSp: Log each bit of output as it arrives so you can tail the log file and see what it is doing in "realtime".
                    Log.WriteLine(args.Data);
                }
            }
        }
        #endregion

        #region Methods
        public static int ExecuteCmd(string command, ProcessPriorityClass priorityClass, double timeoutSec)
        {
            // BrianSp: Running processes through cmd.exe allows finer control of output handling, and also 
            // allows the command string to be a cmd or bat file.
            return Execute("cmd.exe", String.Format("/q /c {0} 2>&1", command), priorityClass, timeoutSec);
        }

        public static int Execute(string command, string args, ProcessPriorityClass priorityClass, double timeoutSec)
        {
            Log.WriteLine(String.Format("Executing: {0} {1}", command, args));

            using (System.Diagnostics.Process process = new System.Diagnostics.Process())
            {
                process.StartInfo.FileName = command;
                process.StartInfo.Arguments = args;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.CreateNoWindow = true;
                process.Start();
                process.PriorityClass = priorityClass;

                Output output = new Output(process);

                // BrianSp: After every timeoutSec seconds, check for new output.  If none, assume the spawned process is hung and kill it.
                while (true)
                {
                    int lastLength = output.Length;

                    if (process.WaitForExit((int)TimeSpan.FromSeconds(timeoutSec).TotalMilliseconds))
                        break;

                    if (output.Length == lastLength)
                    {
                        output.Cancel();

                        if (!process.CloseMainWindow())
                            process.Kill();

                        throw new TimeoutException(string.Format("{0} timed out:\n{1}", command, output.ToString()));
                    }
                }

                return process.ExitCode;
            }
        }
        #endregion
    }
}